#include "w25qxx.h"

#ifdef _W25QXX

#include <stdio.h>
#include <string.h>

// #define W25QXX_LOG

#define TAG = "W25QXX";
#ifdef W25QXX_LOG
#define W25QXX_LOG_DEBUG(TAG, ...) \
	printf(TAG, "[%s]", % s);      \
	printf(TAG, __VA_ARGS__);      \
	printf(TAG, "\r\n");
#else
#define W25QXX_LOG_DEBUG(TAG, ...)
#endif

static w25qxxOps_t *w25qxxOps = NULL;
static w25qxxOps_t w25qxxStruct = {
	NULL,
	NULL,
	NULL,
	NULL,
	0,
};

/**
 * @brief w25qxx delay
 *
 * @param ms
 */
static void w25qxxDelay(uint16_t ms)
{
	(void)ms;
}

/**
 * @brief w25qxx read device id
 *
 * @return uint16_t
 */
uint16_t w25qxxReadId(void)
{
	uint16_t Temp = 0;
	w25qxxOps->w25qxxEnable();
	w25qxxOps->w25qxxReadWrite(0x90);
	w25qxxOps->w25qxxReadWrite(0x00);
	w25qxxOps->w25qxxReadWrite(0x00);
	w25qxxOps->w25qxxReadWrite(0x00);
	Temp |= w25qxxOps->w25qxxReadWrite(0xFF) << 8;
	Temp |= w25qxxOps->w25qxxReadWrite(0xFF);
	w25qxxOps->w25qxxDisable();
	return Temp;
}

/**
 * @brief w25qxx read status register's status
 *
 * @param regno register
 * @return uint8_t
 */
static uint8_t w25qxxReadSR(uint8_t regno)
{
	uint8_t byte = 0, command = 0;
	switch (regno)
	{
	case 1:
		command = W25X_ReadStatusReg1;
		break;
	case 2:
		command = W25X_ReadStatusReg2;
		break;
	case 3:
		command = W25X_ReadStatusReg3;
		break;
	default:
		command = W25X_ReadStatusReg1;
		break;
	}
	w25qxxOps->w25qxxEnable();
	w25qxxOps->w25qxxReadWrite(command);
	byte = w25qxxOps->w25qxxReadWrite(0Xff);
	w25qxxOps->w25qxxDisable();
	return byte;
}

/**
 * @brief w25qxx write status register value
 *
 * @param regno register
 * @param sr value
 */
void w25qxxWriteSR(uint8_t regno, uint8_t sr)
{
	uint8_t command = 0;
	switch (regno)
	{
	case 1:
		command = W25X_WriteStatusReg1;
		break;
	case 2:
		command = W25X_WriteStatusReg2;
		break;
	case 3:
		command = W25X_WriteStatusReg3;
		break;
	default:
		command = W25X_WriteStatusReg1;
		break;
	}
	w25qxxOps->w25qxxEnable();
	w25qxxOps->w25qxxReadWrite(command);
	w25qxxOps->w25qxxReadWrite(sr);
	w25qxxOps->w25qxxDisable();
}

/**
 * @brief enable w25qxx device
 *
 */
static void w25qxxWriteEnable(void)
{
	w25qxxOps->w25qxxEnable();
	w25qxxOps->w25qxxReadWrite(W25X_WriteEnable);
	w25qxxOps->w25qxxDisable();
}

/**
 * @brief disable w25qxx device
 *
 */
void w25qxxWriteDisable(void)
{
	w25qxxOps->w25qxxEnable();
	w25qxxOps->w25qxxReadWrite(W25X_WriteDisable);
	w25qxxOps->w25qxxDisable();
}

/**
 * @brief w25qxx read date
 *
 * @param pBuffer date buffer
 * @param readAddr read date address
 * @param numByteToRead read size
 */
void w25qxxRead(uint8_t *pBuffer, uint32_t readAddr, uint16_t numByteToRead)
{
	uint16_t i;
	w25qxxOps->w25qxxEnable();
	w25qxxOps->w25qxxReadWrite(W25X_ReadData);

	if (w25qxxOps->w25qxxType == W25Q256 || w25qxxOps->w25qxxType == W25Q512)
	{
		w25qxxOps->w25qxxReadWrite((uint8_t)((readAddr) >> 24));
	}
	w25qxxOps->w25qxxReadWrite((uint8_t)((readAddr) >> 16));
	w25qxxOps->w25qxxReadWrite((uint8_t)((readAddr) >> 8));
	w25qxxOps->w25qxxReadWrite((uint8_t)readAddr);
	for (i = 0; i < numByteToRead; i++)
	{
		pBuffer[i] = w25qxxOps->w25qxxReadWrite(0XFF);
	}
	w25qxxOps->w25qxxDisable();
}

/**
 * @brief wait w25qxx busy
 *
 */
static void w25qxxWaitBusy(void)
{
	while ((w25qxxReadSR(1) & 0x01) == 0x01)
		;
}

/**
 * @brief w25qxx write page date
 *
 * @param pBuffer date buffer
 * @param writeAddr write date address
 * @param numByteToWrite write size
 */
static void w25qxxWritePage(uint8_t *pBuffer, uint32_t writeAddr, uint16_t numByteToWrite)
{
	uint16_t i;
	w25qxxWriteEnable(); // SET WEL
	w25qxxOps->w25qxxEnable();
	w25qxxOps->w25qxxReadWrite(W25X_PageProgram);

	if (w25qxxOps->w25qxxType == W25Q256 || w25qxxOps->w25qxxType == W25Q512)
	{
		w25qxxOps->w25qxxReadWrite((uint8_t)((writeAddr) >> 24));
	}
	w25qxxOps->w25qxxReadWrite((uint8_t)((writeAddr) >> 16));
	w25qxxOps->w25qxxReadWrite((uint8_t)((writeAddr) >> 8));
	w25qxxOps->w25qxxReadWrite((uint8_t)writeAddr);
	for (i = 0; i < numByteToWrite; i++)
		w25qxxOps->w25qxxReadWrite(pBuffer[i]);
	w25qxxOps->w25qxxDisable();
	w25qxxWaitBusy();
}

/**
 * @brief w25qxx write date no check
 *
 * @param pBuffer date buffer
 * @param writeAddr write addr
 * @param numByteToWrite write size
 */
static void w25qxxWriteNoCheck(uint8_t *pBuffer, uint32_t writeAddr, uint16_t numByteToWrite)
{
	uint16_t pageremain;
	pageremain = 256 - (writeAddr % 256);
	if (numByteToWrite <= pageremain)
		pageremain = numByteToWrite;
	while (1)
	{
		w25qxxWritePage(pBuffer, writeAddr, pageremain);
		if (numByteToWrite == pageremain)
			break;
		else // NumByteToWrite>pageremain
		{
			pBuffer += pageremain;
			writeAddr += pageremain;

			numByteToWrite -= pageremain;
			if (numByteToWrite > 256)
				pageremain = 256;
			else
				pageremain = numByteToWrite;
		}
	}
}

/**
 * @brief w25qxx erase sector
 *
 * @param dstAddr erase address
 */
void w25qxxEraseSector(uint32_t dstAddr)
{
	dstAddr *= 4096;
	w25qxxWriteEnable();
	w25qxxWaitBusy();
	w25qxxOps->w25qxxEnable();
	w25qxxOps->w25qxxReadWrite(W25X_SectorErase);
	if (w25qxxOps->w25qxxType == W25Q256 || w25qxxOps->w25qxxType == W25Q512)
	{
		w25qxxOps->w25qxxReadWrite((uint8_t)((dstAddr) >> 24));
	}
	w25qxxOps->w25qxxReadWrite((uint8_t)((dstAddr) >> 16));
	w25qxxOps->w25qxxReadWrite((uint8_t)((dstAddr) >> 8));
	w25qxxOps->w25qxxReadWrite((uint8_t)dstAddr);
	w25qxxOps->w25qxxDisable();
	w25qxxWaitBusy();
}

/**
 * @brief w25qxx erase the chip
 *
 */
void w25qxxEraseChip(void)
{
	w25qxxWriteEnable();
	w25qxxWaitBusy();
	w25qxxOps->w25qxxEnable();
	w25qxxOps->w25qxxReadWrite(W25X_ChipErase);
	w25qxxOps->w25qxxDisable();
	w25qxxWaitBusy();
}

/**
 * @brief w25qxx write date
 *
 * @param pBuffer date buffer
 * @param writeAddr date address
 * @param numByteToWrite date size
 */
void w25qxxWrite(uint8_t *pBuffer, uint32_t writeAddr, uint16_t numByteToWrite)
{
	uint8_t w25qxxDateBuffer[4096];
	uint32_t secpos;
	uint16_t secoff;
	uint16_t secremain;
	uint16_t i;
	uint8_t *w25qxxBuffer = NULL;

	w25qxxBuffer = w25qxxDateBuffer;
	secpos = writeAddr / 4096;
	secoff = writeAddr % 4096;
	secremain = 4096 - secoff;
	// W25QXX_LOG_DEBUG(TAG,"ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);
	if (numByteToWrite <= secremain)
		secremain = numByteToWrite;
	while (1)
	{
		w25qxxRead(w25qxxBuffer, secpos * 4096, 4096);
		for (i = 0; i < secremain; i++)
		{
			if (w25qxxBuffer[secoff + i] != 0XFF)
				break;
		}
		if (i < secremain)
		{
			w25qxxEraseSector(secpos);
			for (i = 0; i < secremain; i++)
			{
				w25qxxBuffer[i + secoff] = pBuffer[i];
			}
			w25qxxWriteNoCheck(w25qxxBuffer, secpos * 4096, 4096);
		}
		else
			w25qxxWriteNoCheck(pBuffer, writeAddr, secremain);
		if (numByteToWrite == secremain)
		{
			break;
		}
		else
		{
			secpos++;
			secoff = 0;

			pBuffer += secremain;
			writeAddr += secremain;
			numByteToWrite -= secremain;
			if (numByteToWrite > 4096)
				secremain = 4096;
			else
				secremain = numByteToWrite;
		}
	};
}

/**
 * @brief w25qxx power off device
 *
 */
void w25qxxPowerDown(void)
{
	w25qxxOps->w25qxxEnable();
	w25qxxOps->w25qxxReadWrite(W25X_PowerDown);
	w25qxxOps->w25qxxDisable();
	w25qxxDelay(1);
}

/**
 * @brief w25qxx wake up device
 *
 */
void w25qxxWakeup(void)
{
	w25qxxOps->w25qxxEnable();
	w25qxxOps->w25qxxReadWrite(W25X_ReleasePowerDown);
	w25qxxOps->w25qxxDisable();
	w25qxxDelay(1);
}

void w25qxxInit(void)
{
	uint8_t temp;
	w25qxxOps = &w25qxxStruct;
	w25qxxOps->w25qxxSpiInit();
	w25qxxOps->w25qxxType = w25qxxReadId();
	if (w25qxxOps->w25qxxType == W25Q256 || w25qxxOps->w25qxxType == W25Q512)
	{
		temp = w25qxxReadSR(3);
		if ((temp & 0x01) == 0)
		{
			w25qxxOps->w25qxxEnable();
			w25qxxOps->w25qxxReadWrite(W25X_Enable4ByteAddr);
			w25qxxOps->w25qxxDisable();
		}
	}
	W25QXX_LOG_DEBUG(TAG, "read id = %x\r\n", w25qxxOps->w25qxxType);
	uint8_t W25QXX_Write_Buff[64] = "W25QXX_ReadWriteBytes success,my example!!!";
	uint8_t W25QXX_Read_Buff[64] = {0};

	w25qxxWrite(W25QXX_Write_Buff, 0, sizeof(W25QXX_Write_Buff));
	w25qxxRead(W25QXX_Read_Buff, 0, sizeof(W25QXX_Write_Buff));
	W25QXX_LOG_DEBUG(TAG, "%s\r\n", W25QXX_Read_Buff);
	if (strcmp((char *)W25QXX_Write_Buff, (char *)W25QXX_Read_Buff) != 0)
	{
		W25QXX_LOG_DEBUG(TAG, "Flash Init Fail\r\n");
		return;
	}
	W25QXX_LOG_DEBUG(TAG, "Flash Init Success\r\n");
}

#endif
